package main

import (
	"bytes"
	"crypto/md5"
	"crypto/tls"
	"encoding/base64"
	"flag"
	"fmt"
	"net"
	"net/smtp"
	"os"
	"strconv"
	"time"
)

func main() {
	config := parseFlags()

	if err := validateConfig(config); err != nil {
		exitWithError(err)
	}

	message, err := buildMessage(config)
	if err != nil {
		exitWithError(err)
	}

	if err := sendSMTP(config, message); err != nil {
		exitWithError(err)
	}

	fmt.Println("邮件发送成功!")
}

// 在 Config 结构体中添加 ContentText 字段
type Config struct {
	Host        string
	Port        string
	User        string
	Pass        string
	To          string
	Subject     string
	Date        string
	Help        bool
	Content     string // 文件路径
	ContentText string // 直接文本内容
	Attach      string
}

// 在 parseFlags 函数中添加新参数
func parseFlags() Config {
	var cfg Config
	flag.StringVar(&cfg.Host, "host", "", "SMTP服务器地址 (如: smtp.qq.com)")
	flag.StringVar(&cfg.Port, "port", "25", "SMTP服务器端口 (25/465/587)")
	flag.StringVar(&cfg.User, "user", "", "SMTP账号")
	flag.StringVar(&cfg.Pass, "pass", "", "SMTP密码/授权码")
	flag.StringVar(&cfg.To, "to", "", "收件人邮箱")
	flag.StringVar(&cfg.Subject, "subject", "测试邮件", "邮件主题")
	flag.StringVar(&cfg.Date, "d", "", "当月md5sum验证码")
	flag.StringVar(&cfg.Content, "content", "", "邮件内容文件路径 (可选)")
	flag.StringVar(&cfg.ContentText, "content-text", "", "邮件内容文本 (可选)")
	flag.StringVar(&cfg.Attach, "attach", "", "附件文件路径 (可选)")
	flag.BoolVar(&cfg.Help, "help", false, "显示帮助")
	flag.Parse()
	return cfg
}

func validateConfig(cfg Config) error {
	if cfg.Help {
		printHelp()
		os.Exit(0)
	}

	if cfg.Host == "" || cfg.User == "" || cfg.Pass == "" || cfg.To == "" || cfg.Date == "" {
		return fmt.Errorf("错误: 缺少必要参数\n使用 -help 查看帮助")
	}

	// 计算当月md5sum
	currentMonth := time.Now().Format("01")
	hashed := fmt.Sprintf("%x", md5.Sum([]byte(currentMonth)))
	if cfg.Date != hashed {
		return fmt.Errorf("验证码错误")
	}

	return nil
}

func printHelp() {
	fmt.Println("SMTP邮件客户端使用说明")
	fmt.Println("参数：")
	flag.PrintDefaults()
	fmt.Println("\n示例：")
	fmt.Println("  从文件读取内容:")
	fmt.Println("    ./smtp-client -host smtp.qq.com -port 465 -user you@qq.com -pass xxxx \\")
	fmt.Println("      -to target@example.com -d $(echo -n $(date +%m) | md5sum | cut -d' ' -f1) \\")
	fmt.Println("      -subject \"重要通知\" -content message.txt -attach report.pdf")
	fmt.Println("  直接指定内容文本:")
	fmt.Println("    ./smtp-client -host smtp.qq.com -port 465 -user you@qq.com -pass xxxx \\")
	fmt.Println("      -to target@example.com -d $(echo -n $(date +%m) | md5sum | cut -d' ' -f1) \\")
	fmt.Println("      -subject \"重要通知\" -content-text \"这是邮件内容\"")
	fmt.Println("\n生成验证码方法(Linux/Mac):")
	fmt.Println("  echo -n $(date +%m) | md5sum | cut -d' ' -f1")
}

func buildMessage(cfg Config) ([]byte, error) {
	var body string
	// 优先使用直接输入的文本内容
	if cfg.ContentText != "" {
		body = cfg.ContentText
	} else if cfg.Content != "" {
		// 如果没有直接文本内容，则尝试读取文件
		contentBytes, err := os.ReadFile(cfg.Content)
		if err != nil {
			return nil, fmt.Errorf("读取内容文件失败: %v", err)
		}
		body = string(contentBytes)
	} else {
		// 如果都没有指定，使用默认内容
		currentTime := time.Now().Format("2006-01-02 15:04:05")
		body = fmt.Sprintf("这是一封测试邮件\n\n发送时间：%s", currentTime)
	}

	var attachment []byte
	if cfg.Attach != "" {
		var err error
		attachment, err = os.ReadFile(cfg.Attach)
		if err != nil {
			return nil, fmt.Errorf("读取附件失败: %v", err)
		}
	}

	buf := bytes.NewBuffer(nil)
	buf.WriteString(fmt.Sprintf("From: %s\r\n", cfg.User))
	buf.WriteString(fmt.Sprintf("To: %s\r\n", cfg.To))
	buf.WriteString(fmt.Sprintf("Subject: %s\r\n", cfg.Subject))

	if len(attachment) > 0 {
		boundary := "boundary_" + strconv.FormatInt(time.Now().UnixNano(), 10)
		buf.WriteString(fmt.Sprintf("MIME-Version: 1.0\r\n"))
		buf.WriteString(fmt.Sprintf("Content-Type: multipart/mixed; boundary=\"%s\"\r\n\r\n", boundary))
		buf.WriteString(fmt.Sprintf("--%s\r\n", boundary))
		buf.WriteString("Content-Type: text/plain; charset=utf-8\r\n\r\n")
		buf.WriteString(body)
		buf.WriteString(fmt.Sprintf("\r\n--%s\r\n", boundary))
		buf.WriteString("Content-Type: application/octet-stream\r\n")
		buf.WriteString("Content-Transfer-Encoding: base64\r\n")
		buf.WriteString(fmt.Sprintf("Content-Disposition: attachment; filename=\"%s\"\r\n\r\n", cfg.Attach))

		b := make([]byte, base64.StdEncoding.EncodedLen(len(attachment)))
		base64.StdEncoding.Encode(b, attachment)
		buf.Write(b)
		buf.WriteString(fmt.Sprintf("\r\n--%s--\r\n", boundary))
	} else {
		buf.WriteString("\r\n")
		buf.WriteString(body)
	}

	return buf.Bytes(), nil
}

func sendSMTP(cfg Config, message []byte) error {
	port, err := strconv.Atoi(cfg.Port)
	if err != nil {
		return fmt.Errorf("无效端口号: %v", err)
	}

	switch port {
	case 465:
		return sendWithDirectTLS(cfg, message)
	case 587:
		return sendWithSTARTTLS(cfg, message, true)
	case 25:
		return sendWithSTARTTLS(cfg, message, false)
	default:
		// For custom ports, try all methods in order: plain -> STARTTLS -> SSL
		return sendWithCustomPort(cfg, message, port)
	}
}

func sendWithCustomPort(cfg Config, message []byte, port int) error {
	errors := []string{}

	// Try 1: Plain connection without encryption
	fmt.Printf("尝试明文连接到 %s:%d...\n", cfg.Host, port)
	err := sendWithPlainConnection(cfg, message, port)
	if err == nil {
		return nil
	}
	errors = append(errors, fmt.Sprintf("明文连接失败: %v", err))

	// Try 2: STARTTLS
	fmt.Printf("尝试STARTTLS连接到 %s:%d...\n", cfg.Host, port)
	err = sendWithSTARTTLSForCustomPort(cfg, message, port)
	if err == nil {
		return nil
	}
	errors = append(errors, fmt.Sprintf("STARTTLS连接失败: %v", err))

	// Try 3: Direct SSL/TLS
	fmt.Printf("尝试SSL连接到 %s:%d...\n", cfg.Host, port)
	err = sendWithDirectTLSForCustomPort(cfg, message, port)
	if err == nil {
		return nil
	}
	errors = append(errors, fmt.Sprintf("SSL连接失败: %v", err))

	// All methods failed
	return fmt.Errorf("所有连接方法都失败:\n%s", fmt.Sprintf("- %s\n- %s\n- %s", errors[0], errors[1], errors[2]))
}

func sendWithPlainConnection(cfg Config, message []byte, port int) error {
	client, err := smtp.Dial(fmt.Sprintf("%s:%d", cfg.Host, port))
	if err != nil {
		return err
	}
	defer client.Close()

	return doSMTPAuthAndSend(client, cfg, message)
}

func sendWithSTARTTLSForCustomPort(cfg Config, message []byte, port int) error {
	client, err := smtp.Dial(fmt.Sprintf("%s:%d", cfg.Host, port))
	if err != nil {
		return err
	}
	defer client.Close()

	// Check if STARTTLS is supported
	if ok, _ := client.Extension("STARTTLS"); !ok {
		return fmt.Errorf("服务器不支持STARTTLS")
	}

	// Perform STARTTLS
	if err := client.StartTLS(&tls.Config{
		InsecureSkipVerify: true,
		ServerName:         cfg.Host,
	}); err != nil {
		return fmt.Errorf("STARTTLS失败: %v", err)
	}

	return doSMTPAuthAndSend(client, cfg, message)
}

func sendWithDirectTLSForCustomPort(cfg Config, message []byte, port int) error {
	conn, err := tls.Dial("tcp", fmt.Sprintf("%s:%d", cfg.Host, port), &tls.Config{
		InsecureSkipVerify: true,
		ServerName:         cfg.Host,
	})
	if err != nil {
		return fmt.Errorf("TLS连接失败: %v", err)
	}
	defer conn.Close()

	return doSMTPTransaction(conn, cfg, message)
}

func sendWithDirectTLS(cfg Config, message []byte) error {
	conn, err := tls.Dial("tcp", fmt.Sprintf("%s:465", cfg.Host), &tls.Config{
		InsecureSkipVerify: true,
		ServerName:         cfg.Host,
	})
	if err != nil {
		return fmt.Errorf("TLS连接失败: %v", err)
	}
	defer conn.Close()

	return doSMTPTransaction(conn, cfg, message)
}

func sendWithSTARTTLS(cfg Config, message []byte, requireTLS bool) error {
	client, err := smtp.Dial(fmt.Sprintf("%s:%s", cfg.Host, cfg.Port))
	if err != nil {
		return fmt.Errorf("连接服务器失败: %v", err)
	}
	defer client.Close()

	if ok, _ := client.Extension("STARTTLS"); ok {
		if err := client.StartTLS(&tls.Config{
			InsecureSkipVerify: true,
			ServerName:         cfg.Host,
		}); err != nil {
			return fmt.Errorf("STARTTLS失败: %v", err)
		}
	} else if requireTLS {
		return fmt.Errorf("服务器不支持STARTTLS")
	}

	return doSMTPAuthAndSend(client, cfg, message)
}

func doSMTPTransaction(conn net.Conn, cfg Config, message []byte) error {
	client, err := smtp.NewClient(conn, cfg.Host)
	if err != nil {
		return fmt.Errorf("创建SMTP客户端失败: %v", err)
	}
	defer client.Close()

	return doSMTPAuthAndSend(client, cfg, message)
}

func doSMTPAuthAndSend(client *smtp.Client, cfg Config, message []byte) error {
	auth := smtp.PlainAuth("", cfg.User, cfg.Pass, cfg.Host)
	if err := client.Auth(auth); err != nil {
		return fmt.Errorf("SMTP认证失败: %v", err)
	}

	if err := client.Mail(cfg.User); err != nil {
		return fmt.Errorf("设置发件人失败: %v", err)
	}

	if err := client.Rcpt(cfg.To); err != nil {
		return fmt.Errorf("设置收件人失败: %v", err)
	}

	w, err := client.Data()
	if err != nil {
		return fmt.Errorf("准备发送数据失败: %v", err)
	}
	defer w.Close()

	if _, err = w.Write(message); err != nil {
		return fmt.Errorf("写入邮件内容失败: %v", err)
	}

	return nil
}

func exitWithError(err error) {
	fmt.Println(err)
	os.Exit(1)
}
